<?php
/**
 * Created by PhpStorm.
 * User: DELL-PC
 * Date: 2018/8/2
 * Time: 12:12
 */

namespace J\Schema\Collection;


use J\Schema\ICurd;
use J\Schema\IElement;
use J\Schema\IWhere;
use J\Schema\Sprite;
use J\Schema\Syntax;
use J\Exception;
use J\IQueryablePersister;
use J\Message;

/**
 * 计划： status = 0 用作正常
 *       级联删除采用按位形式
 *
 *
 *
 * @method int max() max($field = '*', $where = true, $model = null) 最大值
 * @method int min() min($field = '*', $where = true, $model = null) 最小值
 * @method int count() count($field = '*', $where = true, $model = null) 非空行数
 * @method int sum() sum($field = '*', $where = true, $model = null) 和
 */
class Curd implements ICurd
{
    /**
     * @var ICollection 集合
     */
    protected $poem;

    /**
     * @var string[] 操作元素列表
     */
    protected $elements;

    /**
     * @var IQueryablePersister 存储器
     */
    protected $driver;

    /**
     * @var int|int[]
     */
    protected $ids = null;

    /**
     * @var Where
     */
    protected $objWhere;

    /**
     * @var boolean
     */
    protected $recyclable;

    /**
     * @var
     */
    protected $raw = false;

    /**
     * @var Option
     */
    protected $objOptions;

    /**
     * @var bool
     */
    protected $connect = true;

    /**
     * Curd constructor.
     * @param ICollection $poem
     */
    public function __construct(ICollection $poem)
    {
        $this->poem = $poem;
        $this->driver = app()->Orm($poem->getTableName());
    }

    public function query($fetch_model = null)
    {
        $columns = [];
        $this->poem->elements()->iterateElements(function (IElement $element) use (&$columns) {
            $columns[] = $element->getColumnName();
        }, $this->elements);
        return $this->runQuery(
            $columns,
            $fetch_model
        );
    }

    public function select()
    {
        $names = [];
        $columns = [];
        $this->poem->elements()->iterateElements(
            function (IElement $element) use (&$names, &$columns){
                $names[$element->getName()] = true;
                $columns[$element->getColumnName()] = true;
            },
            $this->elements
        );

        //没有查询域
        if (empty($names) or $names === [Syntax::KEY_ID]) {
            return array_fill_keys($this->getIds() ?: [], []);
        }

        //没有ID
        if (empty($this->getIds())) return [];

        $rs = $this->driver->getMulti($this->getIds(), array_keys($columns));

        if ($rs) {
            foreach ($names as $k => $f) {
                $this->poem->elements()->getElement($k)->fillValue($rs, $this->raw);
            }
        }

        return $rs;
    }

    public function find()
    {
        $rs = $this->select();

        if (empty($rs)) return [];

        $r = array_values($rs);
        return $r[0];
    }

    public function edit()
    {
        if (empty($this->getIds())) return 0;

        $exp = [];

        $this->poem->elements()->iterateElements(function (IElement $element, $value) use (&$exp){
            $exp[] = $element->getSprite($value)->mapCalculator();
        }, $this->elements, true);

        return $this->driver->set($exp, $this->getIds());
    }

    public function delete()
    {
        $this->doConnection(function (ICurd $curd) {
            $curd->setRecycle($this->recyclable);
            $curd->delete();
        });

        if (!$this->recyclable) return $this->clear();
        return $this->recovery();
    }

    public function clear()
    {
        // 删除所有 STATUS = 0
        if (empty($this->objWhere))
            $this->getWhere()->andCondition([Syntax::KEY_STATUS => 0]);

        if(empty($this->getIds())) return 0;

        return $this->driver->delete($this->getIds());
    }

    public function recycle()
    {
        // TODO 如果存在多个Belongs删除行为，级联恢复存在暴力恢复
        $this->doConnection(function (ICurd $curd) {
            $curd->recycle();
        });

        $this->elements = [Syntax::KEY_STATUS => 1];
        return $this->edit();
    }

    /**
     * 删除到回收站
     *
     * @return int|mixed
     * @throws Exception
     */
    protected function recovery()
    {
        $this->elements = [Syntax::KEY_STATUS => 0];
        return $this->edit();
    }

    public function setIds($ids = null)
    {
        if ($ids === null or $ids === Syntax::KEY_ID) {
            if (!isset($_REQUEST[Syntax::KEY_ID]))
                throw new Exception(Message::ID_NOT_INPUT);

            $this->ids = $_REQUEST[Syntax::KEY_ID];
            return $this;
        }

        if (is_array($ids) and isset($ids[Syntax::KEY_ID])){
            $this->ids = $ids[Syntax::KEY_ID];
            return $this;
        }

        if (!convert_id($ids)) throw new Exception(Message::IDS_PARSE_ERROR);

        $this->ids = $ids;
        return $this;
    }

    public function getIds()
    {
        if ($this->ids !== null) return $this->ids;

        if ($this->objWhere) {
            $condition = $this->objWhere->getCondition();
            $this->ids = $this->driver->getKeys($condition, $this->getOption()->getOptions());
        } else{
            $this->ids = [];
        }

        return $this->ids;
    }

    public function setWhere($value)
    {
        if ($value instanceof IWhere) {
            $this->objWhere = $value;
        } else {
            $this->getWhere()->andCondition($value);
        }

        return $this;
    }

    public function getWhere()
    {
        if (empty($this->objWhere)) {
            $this->objWhere = new Where($this->poem);
        }

        return $this->objWhere;
    }

    public function setRecycle($recyclable)
    {
        $this->recyclable = $recyclable;
        return $this;
    }

    public function isRecyclable()
    {
        return $this->recyclable;
    }

    public function setElements($elements)
    {
        $this->elements = $elements;

        return $this;
    }

    public function add($id = 0)
    {
        $exp = [];

        $names = [];

        $values = [];

        // 植入ID
        if ($id !== 0) $names[Syntax::KEY_ID] = $id;

        // 处理输入参数
        $this->poem->elements()->iterateElements(function (IElement $element, $value) use (&$names, &$values){
            $name = $element->getName();
            $names[$name] = true;
            $values[$name] = $value;
        }, $this->elements);

        // 查询未处理的元素
        $this->poem->elements()->iterateElements(function (IElement $element) use (&$names, &$values){
            $name = $element->getName();
            if (isset($names[$name])) return;
            $names[$name] = false;
            $values[$name] = null;
        });

        // 统一处理
        foreach ($names as $name => $v) {
            $element = $this->poem->elements()->getElement($name);
            // 跳过非必须元素
            // if (!$v and !$element->must()) continue;
            //
            $exp[] = $element->getSprite($values[$name])->mapCalculator();
        }


        return $this->driver->add($exp);
    }

    public function setRaw($raw)
    {
        $this->raw = $raw;
        return $this;
    }

    public function setOption($option)
    {
        $this->objOptions = $option;

        return $this;
    }

    public function getOption()
    {
        if (!$this->objOptions) {
            $this->objOptions = new Option($this->poem);
        }
        return $this->objOptions;
    }

    /**
     * 处理统计数据
     * @param string $name 统计函数
     * @param $arguments
     * @return mixed
     * @throws Exception
     */
    public function __call($name, $arguments)
    {
        return $this->statistics($name);
    }

    /**
     * 统计查询
     * @param $name
     * @return int|null
     * @throws Exception
     */
    public function statistics($name)
    {

        if (in_array($name, ['count', 'sum', 'max', 'min', 'avg'])) {


            $element = $name . '(' . $this->elements[0] . ')';

            $r = $this->runQuery($element, \PDO::FETCH_NUM);

            return (int)($r[0][0]);

        }
        return null;
    }

    /**
     * 执行查询
     * @param $expression
     * @param int $fetch_model
     * @return mixed
     * @throws Exception
     */
    protected function runQuery($expression, $fetch_model = null)
    {
        return $this->driver->query(
            $expression,
            $this->objWhere->getCondition(),
            $this->objOptions->getOptions(),
            $fetch_model
        );
    }

    public function extra()
    {
        return new CurdExtra($this);
    }

    public function relation()
    {
        $r = new CurdRelation($this->poem);
        $r->setElements($this->elements)
            ->setIds($this->ids)
            ->setWhere($this->getWhere())
            ->setOption($this->getOption())
            ->setRecycle($this->isRecyclable())
            ->setRaw($this->raw);
        return $r;
    }

    public function getCollection()
    {
        return $this->poem;
    }

    /**
     * @return bool
     */
    public function isConnect(): bool
    {
        return $this->connect;
    }

    /**
     * @param bool $connect
     */
    public function setConnect(bool $connect): void
    {
        $this->connect = $connect;
    }

    /**
     * 级联 删除/恢复
     *
     * @param \Closure $func
     * @throws Exception
     */
    protected function doConnection(\Closure $func)
    {
        if (!$this->isConnect()) return;

        $this->poem->getCollections()->iterateCollection(function (ICollection $poem) use ($func) {
            $name = $this->poem->getName();
            if (! $poem->belongs($name)) return;

            $ids = $this->getIds();
            $where = new Sprite($name, $ids, Syntax::OPERATOR_IN);
            $curd = $poem->getCurd()->setWhere([$where]);
            $func($curd);
        });
    }
}